Class 类 – 反射的基础
Class 类是什么
我们可以自己自定义类,JDK 中也提供了很多类,这些类有自己的名字,可能还有个爹(父类),也有自己的属性,比如:是否是接口?是不是基本数据类型等等。
从这个角度看,类跟现实世界的事物一样,有自己的特点。既然我们可以创建一个类来表示某种事物,那么是否可以设计一个类来表示“类”呢?
答案是可以的,而且已经有了这个类了,这就是 Class 类。
获取 Class 类实例的三种方法
Class 类是用来表示一个类的,我们已经知道可以定义一个 Student 类表示学生,然后可以调用 Student 对象的方法(比如 getName() 方法)获取到这个学生的信息。
这里也是一样的,通过调用 Class 对象的方法就可以获取到某个类的信息。比如 getName() 方法就可以获取到类的名称。
所有我们首先应该做的就是创建 Class 类型对象。
方法一
通过已存在的对象创建,此处使用的是 getClass() 方法
1 | Employee e; |
注意:Class 是泛型类,明确类型时可以写清楚:
1 | Class<Employee> |
不知道的情况下可以写个问号:
1 | Class<?> |
方法二
通过一个字符串来创建
1 | Class cl = Class.forName("java.util.Date"); |
方法三
通过 类名.class 来创建
1 | Class c1 = int.class; |
反射的意义
通过上面的讲解,可以了解到 Class 类型的对象可以获取到某个类的各种信息,但实际上不仅仅可以获取信息,还可以由 Class 类型的对象创建出某个类的实例(通过 newInstance() 方法),也可以执行这个类里面的静态方法或普通方法,甚至可以在运行的时候修改这个类的实例的成员变量(比如通过 Class 对象,我可以修改一个 Student 对象里面的私有变量的值)。
暂时看不懂没关系,下面会用一些例子来说明。
需要留意的一点,通过 forName() 方法创建一个 Class 对象,比如
1 | Class<?> cl = Class.forName("反射.Student"); |
并不会创建一个 Student 对象,所以 Student 类的初始化块并不会执行。
但是 Student 类的静态字段会被执行(用 static 修饰的代码块)。
Class 类部分方法介绍
- getName() 获取类名
- isInterface() 是否是接口
- isPrimitive() 是否是基本类型
- isArray() 是否为数组对象
- getSuperclass() 获取父类 Class 对象,可能得到 null
- getPackage() 获取类所在包
Class 类的实际运用
判断对象所属类型
1 | if(e.getClass().getName() == "Employee") |
不管每个类型创建了多少个实例,也不管你用什么方法获取此类型的 Class 实例,每个类型都只对应一个 Class 实例。
基本数据类型,比如 int,float 等,也有一个对应的 Class 实例,它并不等于包装类的 Class 实例,包装类另提供了一个 TYPE 字段,向外界返回它所包装的基本数据类型的 Class 实例,所以 int.class == Integer.TYPE,其值为 true。
运行时动态加载类并进行查询
使用 Class.forName() 方法可以加载指定名字的类型信息到内存中以便查询,所以在可以运行时获取用户输入的类名,然后加载一个类,也就是上面所说的方法二。加载类之后就可以查询其成员的方法。
提示:指定类所拥有的属性、方法、构造函数分别由 java.lang.reflect 包中的 Field、Method 和 Constructor 三个类来表达。
1 | /** |
获取某个类的构造方法
获取类的所有构造方法:
1 | 获取类的所有构造方法: |
有了类的构造方法的引用,就可以用它来创建对象。
动态创建对象的典型方法
一种是通过 Class 对象的 newInstance() 方法动态创建,另一种是通过构造方法对象(Constructor 对象)的 newInstance() 方法动态创建。
1 |
|
动态创建数组
1 | import java.lang.reflect.Array; |
强类型的对象工厂
所谓对象工厂,实际上应该只是一个方法吧。利用对象工厂可以避免重复的编写 newInstance() 的代码。
通过字符串可以创建一个 Class 对象,通过 Class 对象可以动态的创建出一个对象,但是这种方法需要强制类型转换(newInstance() 方法返回的 Object 类型对象)。
1 | package cn.edu.bit.cs.factory; |
利用泛型技术,可以不用强制类型转换,但是这样不能通过用户输入来创建 Class 实例了。(重点在于实例化 Class 对象的时候,明确是哪个类的 Class 对象。)
1 | package 反射; |
利用反射存取对象的字段
有一个 Field 类,可以和一个类的字符绑定起来。(绑定的时候不需要跟具体的对象关联起来。)通过 Field 对象的 getField() 和 setField() 方法可以获取、修改一个对象的值。(此时需要跟具体的对象绑定起来,这也很好理解,不绑定谁知道你要修改的是那个对象的字段?)
下面是一个简单的例子:
1 | import java.lang.reflect.Field; |
再来一个修改私有变量的例子。
注意,Field 绑定时使用的方法有所不用,而且需要用一条语句,设置可以访问私有变量。
1 | import java.lang.reflect.Field; |
通过反射调用类的方法
通过 Class 对象的 getMethdo() 方法获取到某个类的方法,返回的是一个 Method 对象,然后通过 Method 对象的 invoke() 方法可以调用该方法。(invoke() 方法的两个参数是:对象引用和实际参数。这里的 main() 是静态方法,所以不用指明对象,而且没有形参,所以 invoke() 没有第二个参数。)
1 | import java.lang.reflect.Method; |
简单的反射框架
如果我们将类的名字,要对应的方法等放到一个外部的配置文件中,而让程序在运行时使用反射去读取它们,进而创建相应的对象并自动调用特定的方法,我们就创建了一个简单的程序框架。